// For license of this file, see <project-root-folder>/LICENSE.md.

#include "gui/webbrowser.h"

#include "gui/discoverfeedsbutton.h"
#include "gui/locationlineedit.h"
#include "gui/messagebox.h"
#include "gui/searchtextwidget.h"
#include "gui/webviewer.h"
#include "miscellaneous/application.h"
#include "miscellaneous/databasequeries.h"
#include "network-web/networkfactory.h"
#include "network-web/webfactory.h"
#include "services/abstract/serviceroot.h"

#include <QKeyEvent>
#include <QScrollBar>
#include <QToolBar>
#include <QToolTip>
#include <QWebEngineSettings>
#include <QWidgetAction>

WebBrowser::WebBrowser(QWidget* parent) : TabContent(parent),
  m_layout(new QVBoxLayout(this)),
  m_toolBar(new QToolBar(tr("Navigation panel"), this)),
  m_webView(new WebViewer(this)),
  m_searchWidget(new SearchTextWidget(this)),
  m_txtLocation(new LocationLineEdit(this)),
  m_btnDiscoverFeeds(new DiscoverFeedsButton(this)),
  m_actionBack(m_webView->pageAction(QWebEnginePage::Back)),
  m_actionForward(m_webView->pageAction(QWebEnginePage::Forward)),
  m_actionReload(m_webView->pageAction(QWebEnginePage::Reload)),
  m_actionStop(m_webView->pageAction(QWebEnginePage::Stop)) {
  // Initialize the components and layout.
  initializeLayout();
  setFocusProxy(m_txtLocation);
  setTabOrder(m_txtLocation, m_toolBar);
  setTabOrder(m_toolBar, m_webView);
  createConnections();
  reloadFontSettings();
}

void WebBrowser::createConnections() {
  installEventFilter(this);

  connect(m_searchWidget, &SearchTextWidget::cancelSearch, this, [this]() {
    m_webView->findText(QString());
  });
  connect(m_searchWidget, &SearchTextWidget::searchForText, this, [this](const QString& text, bool backwards) {
    if (backwards) {
      m_webView->findText(text, QWebEnginePage::FindBackward);
    }
    else {
      m_webView->findText(text);
    }

    m_searchWidget->setFocus();
  });

  connect(m_webView, &WebViewer::messageStatusChangeRequested, this, &WebBrowser::receiveMessageStatusChangeRequest);
  connect(m_txtLocation, &LocationLineEdit::submitted,
          this, static_cast<void (WebBrowser::*)(const QString&)>(&WebBrowser::loadUrl));
  connect(m_webView, &WebViewer::urlChanged, this, &WebBrowser::updateUrl);

  // Change location textbox status according to webpage status.
  connect(m_webView, &WebViewer::loadStarted, this, &WebBrowser::onLoadingStarted);
  connect(m_webView, &WebViewer::loadProgress, this, &WebBrowser::onLoadingProgress);
  connect(m_webView, &WebViewer::loadFinished, this, &WebBrowser::onLoadingFinished);

  // Forward title/icon changes.
  connect(m_webView, &WebViewer::titleChanged, this, &WebBrowser::onTitleChanged);
  connect(m_webView, &WebViewer::iconChanged, this, &WebBrowser::onIconChanged);

  connect(m_webView->page(), &WebPage::windowCloseRequested, this, &WebBrowser::closeRequested);
}

void WebBrowser::updateUrl(const QUrl& url) {
  m_txtLocation->setText(url.toString());

  //setNavigationBarVisible(url_string != INTERNAL_URL_EMPTY && url_string != INTERNAL_URL_NEWSPAPER);
}

void WebBrowser::loadUrl(const QUrl& url) {
  if (url.isValid()) {
    m_webView->load(url);
  }
}

WebBrowser::~WebBrowser() {
  // Delete members. Do not use scoped pointers here.
  delete m_layout;
}

void WebBrowser::reloadFontSettings() {
  QFont fon;

  fon.fromString(qApp->settings()->value(GROUP(Messages),
                                         SETTING(Messages::PreviewerFontStandard)).toString());
  QWebEngineSettings::globalSettings()->setFontFamily(QWebEngineSettings::StandardFont, fon.family());
  QWebEngineSettings::globalSettings()->setFontSize(QWebEngineSettings::DefaultFontSize, fon.pointSize());
}

void WebBrowser::increaseZoom() {
  m_webView->increaseWebPageZoom();
}

void WebBrowser::decreaseZoom() {
  m_webView->decreaseWebPageZoom();
}

void WebBrowser::resetZoom() {
  m_webView->resetWebPageZoom();
}

void WebBrowser::clear() {
  m_webView->clear();
  m_messages.clear();
  hide();
}

void WebBrowser::loadUrl(const QString& url) {
  return loadUrl(QUrl::fromUserInput(url));
}

void WebBrowser::loadMessages(const QList<Message>& messages, RootItem* root) {
  m_messages = messages;
  m_root = root;

  if (!m_root.isNull()) {
    m_searchWidget->hide();
    m_webView->loadMessages(messages, root);
    show();
  }
}

void WebBrowser::loadMessage(const Message& message, RootItem* root) {
  loadMessages(QList<Message>() << message, root);
}

bool WebBrowser::eventFilter(QObject* watched, QEvent* event) {
  Q_UNUSED(watched)

  if (event->type() == QEvent::KeyPress) {
    QKeyEvent* key_event = static_cast<QKeyEvent*>(event);

    if (key_event->matches(QKeySequence::StandardKey::Find)) {
      m_searchWidget->clear();
      m_searchWidget->show();
      m_searchWidget->setFocus();
      return true;
    }
  }

  return false;
}

void WebBrowser::receiveMessageStatusChangeRequest(int message_id, WebPage::MessageStatusChange change) {
  switch (change) {
    case WebPage::MarkRead:
      markMessageAsRead(message_id, true);
      break;

    case WebPage::MarkUnread:
      markMessageAsRead(message_id, false);
      break;

    case WebPage::MarkStarred:
      switchMessageImportance(message_id, true);
      break;

    case WebPage::MarkUnstarred:
      switchMessageImportance(message_id, false);
      break;

    default:
      break;
  }
}

void WebBrowser::onTitleChanged(const QString& new_title) {
  if (new_title.isEmpty()) {
    //: Webbrowser tab title when no title is available.
    emit titleChanged(m_index, tr("No title"));
  }
  else {
    emit titleChanged(m_index, new_title);
  }
}

void WebBrowser::onIconChanged(const QIcon& icon) {
  emit iconChanged(m_index, icon);
}

void WebBrowser::initializeLayout() {
  m_toolBar->setFloatable(false);
  m_toolBar->setMovable(false);
  m_toolBar->setAllowedAreas(Qt::TopToolBarArea);

  // Modify action texts.
  m_actionBack->setText(tr("Back"));
  m_actionBack->setToolTip(tr("Go back."));
  m_actionForward->setText(tr("Forward"));
  m_actionForward->setToolTip(tr("Go forward."));
  m_actionReload->setText(tr("Reload"));
  m_actionReload->setToolTip(tr("Reload current web page."));
  m_actionStop->setText(tr("Stop"));
  m_actionStop->setToolTip(tr("Stop web page loading."));
  QWidgetAction* act_discover = new QWidgetAction(this);

  act_discover->setDefaultWidget(m_btnDiscoverFeeds);

  // Add needed actions into toolbar.
  m_toolBar->addAction(m_actionBack);
  m_toolBar->addAction(m_actionForward);
  m_toolBar->addAction(m_actionReload);
  m_toolBar->addAction(m_actionStop);
  m_toolBar->addAction(act_discover);
  m_toolBar->addWidget(m_txtLocation);
  m_loadingProgress = new QProgressBar(this);
  m_loadingProgress->setFixedHeight(5);
  m_loadingProgress->setMinimum(0);
  m_loadingProgress->setTextVisible(false);
  m_loadingProgress->setMaximum(100);
  m_loadingProgress->setAttribute(Qt::WA_TranslucentBackground);

  // Setup layout.
  m_layout->addWidget(m_toolBar);
  m_layout->addWidget(m_webView);
  m_layout->addWidget(m_loadingProgress);
  m_layout->addWidget(m_searchWidget);
  m_layout->setMargin(0);
  m_layout->setSpacing(0);

  m_searchWidget->hide();
}

void WebBrowser::onLoadingStarted() {
  m_btnDiscoverFeeds->clearFeedAddresses();
  m_loadingProgress->show();
}

void WebBrowser::onLoadingProgress(int progress) {
  m_loadingProgress->setValue(progress);
}

void WebBrowser::onLoadingFinished(bool success) {
  if (success) {
    // Let's check if there are any feeds defined on the web and eventually
    // display "Add feeds" button.
    m_webView->page()->toHtml([this](const QString& result) {
      this->m_btnDiscoverFeeds->setFeedAddresses(NetworkFactory::extractFeedLinksFromHtmlPage(m_webView->url(), result));
    });
  }
  else {
    m_btnDiscoverFeeds->clearFeedAddresses();
  }

  m_loadingProgress->hide();
  m_loadingProgress->setValue(0);
}

void WebBrowser::markMessageAsRead(int id, bool read) {
  if (!m_root.isNull()) {
    Message* msg = findMessage(id);

    if (msg != nullptr && m_root->getParentServiceRoot()->onBeforeSetMessagesRead(m_root.data(),
                                                                                  QList<Message>() << *msg,
                                                                                  read ? RootItem::Read : RootItem::Unread)) {
      DatabaseQueries::markMessagesReadUnread(qApp->database()->connection(objectName()),
                                              QStringList() << QString::number(msg->m_id),
                                              read ? RootItem::Read : RootItem::Unread);
      m_root->getParentServiceRoot()->onAfterSetMessagesRead(m_root.data(),
                                                             QList<Message>() << *msg,
                                                             read ? RootItem::Read : RootItem::Unread);
      emit markMessageRead(msg->m_id, read ? RootItem::Read : RootItem::Unread);

      msg->m_isRead = read ? RootItem::Read : RootItem::Unread;
    }
  }
}

void WebBrowser::switchMessageImportance(int id, bool checked) {
  if (!m_root.isNull()) {
    Message* msg = findMessage(id);

    if (msg != nullptr && m_root->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_root.data(),
                                                                                          QList<ImportanceChange>() <<
                                                                                          ImportanceChange(*msg,
                                                                                                           msg
                                                                                                           ->m_isImportant ?
                                                                                                           RootItem
                                                                                                           ::NotImportant :
                                                                                                           RootItem
                                                                                                           ::Important))) {
      DatabaseQueries::switchMessagesImportance(qApp->database()->connection(objectName()),
                                                QStringList() << QString::number(msg->m_id));
      m_root->getParentServiceRoot()->onAfterSwitchMessageImportance(m_root.data(),
                                                                     QList<ImportanceChange>() << ImportanceChange(*msg,
                                                                                                                   msg->m_isImportant ?
                                                                                                                   RootItem::NotImportant :
                                                                                                                   RootItem::Important));
      emit markMessageImportant(msg->m_id, msg->m_isImportant ? RootItem::NotImportant : RootItem::Important);

      msg->m_isImportant = checked;
    }
  }
}

Message* WebBrowser::findMessage(int id) {
  for (int i = 0; i < m_messages.size(); i++) {
    if (m_messages.at(i).m_id == id) {
      return &m_messages[i];
    }
  }

  return nullptr;
}
